/* Copyright(C) 2006-2008 Dave Sexton  *
 * http://www.codeplex.com/aip         *
 * http://www.codeplex.com/aip/license *
 ***************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Configuration.Provider;
using System.Web.UI.WebControls;
using System.Collections.ObjectModel;
using DaveSexton.Web.Controls.Resources;

namespace DaveSexton.Web.Controls
{
	/// <summary>
	/// An implementation of <see cref="AutoInputProtectionFilterProvider"/> that renders an overlay of random translucent bars.  
	/// This class cannot be inherited.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Individual bars are rendered in a single <see cref="Color"/> that is sampled at random from the list of 
	/// <see cref="Colors"/>.  All bars are rendered in the same <see cref="Orientation"/>.
	/// </para>
	/// <para>
	/// You can use this filter to increase the randomization of the images that AIP generates and to change the appearance of images 
	/// to match the aesthetics of the web site.
	/// </para>
	/// </remarks>
	/// <include file='examples.xml' path='//example[@id="TranslucentBarsOverlay"][@type="images"]'/>
	/// <include file='examples.xml' path='//example[@id="TranslucentBarsOverlay"][@type="config"]'/>
	/// <seealso cref="AutoInputProtection.FilterProviders"/>
	/// <seealso cref="Configuration.AutoInputProtectionSection"/>
	/// <seealso cref="AutoInputProtectionTextProvider"/>
	/// <seealso cref="AutoInputProtectionImageProvider"/>
	/// <seealso cref="RandomIntervals"/>
	public sealed class TranslucentBarsOverlayAutoInputProtectionFilterProvider : AutoInputProtectionFilterProvider
	{
		#region Public Properties
		/// <summary>
		/// Gets a list of <see cref="Color">Colors</see> that are sampled at random for each bar.
		/// </summary>
		/// <remarks>
		/// At least one <see cref="Color"/> must be specified or else the <see cref="PostProcess"/> method will throw 
		/// an <see cref="InvalidOperationException"/>.
		/// </remarks>
		/// <value>List of <see cref="Color">Colors</see> from which one will be chosen at random and used to draw the 
		/// lines of the crosshatch.</value>
		public IList<Color> Colors
		{
			get
			{
				return colors;
			}
		}

		/// <summary>
		/// Gets or sets the minimum number of bars.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">The specified value is less than zero.</exception>
		/// <seealso cref="MaximumBarCount"/>
		/// <value>The minimum number of bars that may be randomly generated.</value>
		public int MinimumBarCount
		{
			get
			{
				return min;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Errors.PositiveIntOrZeroRequired);

				min = value;
			}
		}

		/// <summary>
		/// Gets or sets the maximum number of bars.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">The specified value is less than zero.</exception>
		/// <seealso cref="MinimumBarCount"/>
		/// <value>The maximum number of bars that may be randomly generated.</value>
		public int MaximumBarCount
		{
			get
			{
				return max;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Errors.PositiveIntOrZeroRequired);

				max = value;
			}
		}

		/// <summary>
		/// Gets or sets the minimum size when generating random bars.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">The specified value is less than zero.</exception>
		/// <value>The minimum size that may be used for a randomly generated bar.</value>
		public int MinimumBarSize
		{
			get
			{
				return minSize;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Errors.PositiveIntOrZeroRequired);

				minSize = value;
			}
		}

		/// <summary>
		/// Gets or sets whether bars may overlap one another.
		/// </summary>
		/// <value>Indicates whether bars may overlap one another.</value>
		public bool AllowOverlap
		{
			get
			{
				return allowOverlap;
			}
			set
			{
				allowOverlap = value;
			}
		}

		/// <summary>
		/// Gets or sets whether bars are rendered vertically or horizontally.
		/// </summary>
		/// <exception cref="System.ComponentModel.InvalidEnumArgumentException">The specified value is not a valid <see cref="Orientation"/> value.</exception>
		/// <value>Specifies the <see cref="Orientation"/> of all of the bars.</value>
		public Orientation Orientation
		{
			get
			{
				return orientation;
			}
			set
			{
				if (!Enum.IsDefined(typeof(Orientation), value))
					throw new System.ComponentModel.InvalidEnumArgumentException("value", (int) value, typeof(Orientation));

				orientation = value;
			}
		}

		/// <summary>
		/// Gets whether the filter supports preprocessing.
		/// </summary>
		/// <seealso cref="PreProcess"/>
		/// <value>The base implementation always returns <see langword="false"/> to indicate that preprocessing is 
		/// not supported by this filter.</value>
		public override bool CanPreProcess
		{
			get { return false; }
		}

		/// <summary>
		/// Gets whether the filter supports post-processing.
		/// </summary>
		/// <seealso cref="PostProcess"/>
		/// <value>The base implementation always returns <see langword="true"/> to indicate that post-processing is 
		/// supported by this filter.</value>
		public override bool CanPostProcess
		{
			get { return true; }
		}

		/// <summary>
		/// Gets or sets the opacity of the bars as a floating point number between 0 and 1, inclusive, where 0 
		/// is absolute transparency and 1 is absolute opacity.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">The specified value is less than zero or greater than one.</exception>
		/// <value>Opacity of the bars as a floating point number between 0 and 1, inclusive.</value>
		public float Opacity
		{
			get
			{
				return opacity;
			}
			set
			{
				if (value < 0 || value > 1)
					throw new ArgumentOutOfRangeException("value", value, Errors.ValueBetween0And1Inclusive);

				opacity = value;
			}
		}
		#endregion

		#region Private / Protected
		private Orientation orientation;
		private bool allowOverlap;
		private int min, max, minSize;
		private float opacity;
		private readonly List<Color> colors;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TranslucentBarsOverlayAutoInputProtectionFilterProvider" /> class.
		/// </summary>
		public TranslucentBarsOverlayAutoInputProtectionFilterProvider()
		{
			colors = new List<Color>(8);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="TranslucentBarsOverlayAutoInputProtectionFilterProvider" /> class.
		/// </summary>
		/// <param name="colors">A parameter array of <see cref="Color">Colors</see> to be sampled at random when 
		/// drawing the crosshatch.</param>
		public TranslucentBarsOverlayAutoInputProtectionFilterProvider(params Color[] colors)
		{
			if (colors != null && colors.Length > 0)
				this.colors = new List<Color>(colors);
			else
				this.colors = new List<Color>(8);
		}
		#endregion

		#region Methods
		/// <summary>
		/// Loads the provider's settings from the specified <paramref name="config"/>.
		/// </summary>
		/// <exception cref="ProviderException">A value cannot be converted into the required type, or values exist that are not supported by the provider.</exception>
		/// <param name="name">Name of the provider.</param>
		/// <param name="config">A collection of name and value pairs that will be used to initialize the provider.</param>
		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			if (config == null)
				throw new ArgumentNullException("config");

			if (String.IsNullOrEmpty(name))
				name = "TranslucentBarsOverlayAutoInputProtectionFilterProvider";

			if (string.IsNullOrEmpty(config["description"]))
			{
				config.Remove("description");
				config.Add("description", "Translucent Bars Overlay Auto-Input Protection Filter Provider");
			}

			base.Initialize(name, config);

			ProviderHelper helper = new ProviderHelper(config);

			opacity = helper.ParseSingle("opacity", false, .15F);
			min = helper.ParseInt32("minimumBarCount", false, 5);
			max = helper.ParseInt32("maximumBarCount", false, 10);
			minSize = helper.ParseInt32("minimumBarSize", false, 2);
			allowOverlap = helper.ParseBoolean("allowOverlap", false, true);
			orientation = helper.ParseEnum("orientation", false, Orientation.Horizontal);
			colors.AddRange(helper.ParseCollection<Color>("colors", false, true, true, ','));

			// Throw an exception if unrecognized attributes remain
			if (config.Count > 0)
			{
				string attr = config.GetKey(0);

				if (!String.IsNullOrEmpty(attr))
					throw new ProviderException(string.Format(System.Globalization.CultureInfo.CurrentUICulture,
						Errors.UnrecognizedAttribute, attr));
			}
		}

		/// <summary>
		/// This method is not supported.
		/// </summary>
		/// <remarks>
		/// <see cref="NotSupportedException"/> is thrown when this method is invoked.
		/// </remarks>
		/// <seealso cref="CanPreProcess"/>
		/// <param name="graphics"><see cref="Graphics" /> object that is the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		public override void PreProcess(Graphics graphics, Size imageSize)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Applies the filter to the specified <paramref name="graphics" /> object after the text is rendered.
		/// </summary>
		/// <remarks>
		/// <para>
		/// When this method is invoked by AIP, the specified <paramref name="graphics"/> object already has random text, a background image and the results 
		/// from all of the preprocessing filters rendered.  The results from all of the post-processing filters that have executed before this one 
		/// will already be rendered as well.
		/// </para>
		/// <para>
		/// Translucent bars are created using <see cref="RandomIntervals"/> and rendered using a <see cref="SolidBrush"/>, with a random <see cref="Color"/>
		/// for each bar sampled from the list of <see cref="Colors"/>.  All bars are drawn with the same <see cref="Opacity"/> and in the same 
		/// <see cref="Orientation"/>.
		/// </para>
		/// <para>
		/// See <see cref="TranslucentBarsOverlayAutoInputProtectionFilterProvider"/> for examples.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">The specified <paramref name="graphics"/> argument is <see langword="null"/>.</exception>
		/// <exception cref="InvalidOperationException">The <see cref="Colors"/> list is empty or <see cref="MaximumBarCount"/> is less than 
		/// <see cref="MinimumBarCount"/>.</exception>
		/// <seealso cref="CanPostProcess"/>
		/// <param name="graphics"><see cref="Graphics" /> object that is the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		public override void PostProcess(Graphics graphics, Size imageSize)
		{
			if (graphics == null)
				throw new ArgumentNullException("graphics");

			if (colors.Count == 0)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentUICulture, 
					Errors.NoColorsForFilter, Name));

			if (max < min)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentUICulture,
					Errors.TranslucentBarsOverlayAIPFilterProvider_MaxLessThanMin, Name));

			Random rnd = new Random();

			IEnumerable<Interval> intervals = (allowOverlap)
				? RandomIntervals.Create(min, max, minSize, (orientation == Orientation.Vertical) ? imageSize.Width : imageSize.Height)
				: RandomIntervals.CreateIsolated(min, max, minSize, (orientation == Orientation.Vertical) ? imageSize.Width : imageSize.Height);

			int op = Math.Max(0, Math.Min((int) (opacity * 255), 255));

			foreach (Interval interval in intervals)
			{
				Color color = colors[rnd.Next(0, colors.Count)];

				using (SolidBrush brush = new SolidBrush(Color.FromArgb(op, color.R, color.G, color.B)))
				{
					if (orientation == Orientation.Vertical)
						graphics.FillRectangle(brush, interval.Position, 0, interval.Width, imageSize.Height);
					else
						graphics.FillRectangle(brush, 0, interval.Position, imageSize.Width, interval.Width);
				}
			}
		}
		#endregion
	}
}
